#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <errno.h>

#include "p2p_proto.h"
#include "debug.h"

const static sproto_header p2p_header = 
{
	.type = P2P_PROTO_TYPE,
	.name = P2P_PROTO_NAME,
	.version = P2P_PROTO_VER,
	.length = sizeof(sp2p_content),
	.build = p2p_build_package,
	.parse = p2p_parse_package
};
const sproto_header *p2p_default_header = &p2p_header;

static unsigned char cmd_maps[][2] = 
{// {send-cmd,expect-response-cmd}
// 0x00 indicates no response expected
	{0x85,0x80},// device role
	{0x88,0x84},
	{0x81,0x87}, // server role
	{0x89,0x8A},
	{0x8A,0x8B},// device role
	{0x8B,0x00},// server role
	{0x8C,0x00} // device role
};
static unsigned char expected_response_cmd(unsigned char cmd)
{
	int size = sizeof(cmd_maps)/2;
	int res_cmd = 0;
	int i;

	for(i=0;i<size;i++)
	{
		if(cmd == cmd_maps[i][0])
		{
			res_cmd = cmd_maps[0][1];
			break;
		}
	}

	return res_cmd;
}

static int fill_ip_port_helper(unsigned char *buff,const spc_ip_port *ipport)
{
	int i ;
	
	char *ip = NULL;
	char *bak= NULL;
	bak = ip = strdup(ipport->ip);
	for(i=0;ip!=NULL;i++)
	{
		buff[i] = atoi(strsep(&ip,"."))&0xff;
	}
	free(bak);
	buff[4] = ipport->port&0xff;
	buff[5] = (ipport->port>>8)&0xff;

	return 6;
}

static int fill_ip_port(unsigned char *buff,const sip_port_pairs *pair)
{
	int i ;
	
	char *ip = NULL;
	char *bak= NULL;
	bak = ip = strdup(pair->pc_ip);
	for(i=0;ip!=NULL;i++)
	{
		buff[i] = atoi(strsep(&ip,"."))&0xff;
	}
	free(bak);
	buff[4] = pair->pc_port&0xff;
	buff[5] = (pair->pc_port>>8)&0xff;

	bak = ip = strdup(pair->dev_ip);
	for(i=0;ip!=NULL;i++)
	{
		buff[6+i] = atoi(strsep(&ip,"."))&0xff;
	}
	free(bak);
	buff[10] = pair->dev_port&0xff;
	buff[11] = (pair->dev_port>>8)&0xff;

	return 12;
}

static int parse_ip_port(const unsigned char *buff,sip_port_pairs *pair)
{
	snprintf(pair->pc_ip,16,"%u.%u.%u.%u",buff[0],buff[1],buff[2],buff[3]);
	pair->pc_port = buff[4]+(buff[5]<<8);
	snprintf(pair->dev_ip,16,"%u.%u.%u.%u",buff[6],buff[7],buff[8],buff[9]);
	pair->dev_port = buff[10]+(buff[11]<<8);
	return 0;
}

static int build_up_package(unsigned int cmd,up2p_up_content*content,unsigned char *buff,int *len)
{
	int ret = 0;
	
	memset(buff,0,*len);
	switch(cmd)
	{
		case P2P_NEWDEV_LOGINE: // 0x04
			memcpy(buff,content->login.id,9);
			sscanf(content->login.ip,"%hhu.%hhu.%hhu.%hhu",
					&buff[9],&buff[10],&buff[11],&buff[12]);
			buff[13] = content->login.port&0xff;
			buff[14] = (content->login.port>>8)&0xff;
			memcpy(buff+15,content->login.version,16);
			*len = 31;
			break;
		case P2P_HOLE_DONE:
			*len = fill_ip_port(buff,&content->hole_done);
			break;
		case P2P_GET_UDTADDR:
			*len = fill_ip_port(buff,&content->get_udtaddr);
			break;
		case P2P_DEV_HEART:
			buff[0] = content->heart_data.flag;
			buff[1] = content->heart_data.heart_sec&0xff;
			buff[2] = (content->heart_data.heart_sec>>8)&0xff;
			memcpy(buff+3,content->heart_data.id,9);
			*len = 12;
			break;
		case P2P_GET_UDTADDR_AGAIN:
			*len = fill_ip_port(buff,&content->get_udtaddr_inverse);
			break;
		case P2P_MAP_UDT_PORT:
			*len = fill_ip_port(buff,&content->map_udt_port);
			break;
		case P2P_RE_SERVER_NAT:
			buff[0] = content->re_server_nat.net_type;
			buff[1] = content->re_server_nat.route_loop;
			memcpy(buff+2,content->re_server_nat.id,9);
			*len = 11;
			break;
		case P2P_QUERY_DOMAINS:
			memcpy(buff,content->dev_id,9);
			*len = 9;
			break;

		case P2P_QUERY_REGISTE: // 0x01
			memcpy(buff,content->regist_domain_list.id,9);
			memcpy(buff+9,content->regist_domain_list.version,16);
			*len = 25;
			break;
		case P2P_DATA_OK: // device --> client
			memcpy(buff,content->dev_id,9);
			*len = 9;
			break;
		case P2P_RES_TURN_SERVER: // 0x01,res 0xC1
			memcpy(buff,content->res_turn_server.id,9);
			sscanf(content->res_turn_server.ip,"%hhu.%hhu.%hhu.%hhu",
					&buff[9],&buff[10],&buff[11],&buff[12]);
			buff[13] = content->res_turn_server.port&0xff;
			buff[14] = (content->res_turn_server.port>>8)&0xff;
			*len = 15;
			break;
		case P2P_SNDVIDEO_INFORM: // 0x02
			memcpy(buff,content->send_video_inform.id,9);
			memcpy(buff+9,content->send_video_inform.code,6);
			*len = 15;
			break;
		default:
			PLOG(ERR,"cmd[0x%x] is not supported!\n",cmd);
			ret = VPI_EFAIL;
			break;
	}

	return ret;
}

// server role
static int build_down_package(unsigned int cmd,up2p_down_content*content,unsigned char *buff,int *len)
{
	int ret = 0;
	
	memset(buff,0,*len);
	switch(cmd)
	{
		case P2P_RE_LOGIN_DEV:
			buff[0] = content->login_status;
			*len = 1;
			break;
		case P2P_CLIENT_CALL:
			*len = fill_ip_port(buff,&content->client_info);
			break;
		case P2P_HEART_TIME:
			buff[0] = content->heart_sec&0xff;
			buff[1] = (content->heart_sec>>8)&0xff;
			*len = 2;
			break;
		case P2P_YOUR_UDTADDR:
			*len = fill_ip_port(buff,&content->new_link_info);
			break;
		case P2P_RE_HEART:
			buff[0] = 0xff;
			*len = 1;
			break;
		case P2P_CLI_CALL_AGAIN:
			*len = fill_ip_port(buff,&content->client_info_inverse);
			break;
		case P2P_RE_CLIADDR:
			*len = fill_ip_port_helper(buff,&content->res_snd_ok_inverse);		
			break;
		case P2P_CLI_CALL_MAP:
			*len = fill_ip_port_helper(buff,&content->get_udp_map);		
			break;
		case P2P_SERVER_GETNAT:
			*len = snprintf(buff,*len,"%s;%s",content->get_network_nat.id,content->get_network_nat.domain);
			*len += 1;
			break;
		case P2P_SERVER_LIST:
			*len = snprintf(buff,*len,"%s;%s*%u;%s*%u;%s*%u;%s*%u;",content->res_domain_list.dev_domain,
					content->res_domain_list.dns_1.domain,content->res_domain_list.dns_1.port,
					content->res_domain_list.dns_2.domain,content->res_domain_list.dns_2.port,
					content->res_domain_list.p2p_1.domain,content->res_domain_list.p2p_1.port,
					content->res_domain_list.p2p_2.domain,content->res_domain_list.p2p_2.port);
			*len += 1;
			break;
		default:
			ret = VPI_EFAIL;
			break;
	}
	return ret;
}

// server role
static int parse_up_package(unsigned int cmd,up2p_up_content*content,unsigned char *buff,int len)
{
	int ret = 0,num = 0;
	unsigned char ck_sum = buff[len];///!preserve checksum to restore it  
	
	memset(content,0,sizeof(up2p_up_content));
	switch(cmd)
	{
		case P2P_DEV_LOGINE:
			buff[len] = 0;// ensure string ends with null byte
			num = sscanf(buff,"%[^;];%s",content->login.id,content->login.version);
			buff[len] = ck_sum;/// restore checksum
			if(num < 2)
			{
				PLOG(ERR,"P2P_DEV_LOGINE cmd parse error!\n");
				ret = VPI_EPARAMS;
			}
			break;
		case P2P_HOLE_DONE:
			parse_ip_port(buff,&content->hole_done);
			break;
		case P2P_GET_UDTADDR:
			parse_ip_port(buff,&content->get_udtaddr);
			break;
		case P2P_DEV_HEART:
			content->heart_data.flag = buff[0];
			content->heart_data.heart_sec = buff[1]+(buff[2]<<8);
			memcpy(content->heart_data.id,&buff[3],9);
			break;
		case P2P_GET_UDTADDR_AGAIN:
			parse_ip_port(buff,&content->get_udtaddr_inverse);
			break;
		case P2P_MAP_UDT_PORT:
			parse_ip_port(buff,&content->map_udt_port);
			break;
		case P2P_RE_SERVER_NAT:
			content->re_server_nat.net_type = buff[0];
			content->re_server_nat.route_loop = buff[1];
			memcpy(content->re_server_nat.id,&buff[2],9);
			break;
		case P2P_QUERY_DOMAINS:
			memcpy(content->dev_id,buff,9);
			break;

		default:
			PLOG(ERR,"cmd[0x%x] is not supported!\n",cmd);
			ret = VPI_EFAIL;
			break;
	}

	return ret;
}

static int parse_down_package(unsigned int cmd,up2p_down_content*content,unsigned char *buff,int len)
{
	int ret = 0,num = 0;
	unsigned char ck_sum = buff[len];///!preserve checksum to restore it  
	
	memset(content,0,sizeof(up2p_down_content));
	switch(cmd)
	{
		case P2P_RE_LOGIN_DEV:
			content->login_status = buff[0];
			break;
		case P2P_CLIENT_CALL:
			parse_ip_port(buff,&content->client_info);
			break;
		case P2P_HEART_TIME:
			content->heart_sec = buff[0] + (buff[1]<<8);
			break;
		case P2P_YOUR_UDTADDR:
			parse_ip_port(buff,&content->new_link_info);
			break;
		case P2P_RE_HEART: // do nothing
			break;
		case P2P_CLI_CALL_AGAIN:
			parse_ip_port(buff,&content->client_info_inverse);
			break;
		case P2P_RE_CLIADDR:
			snprintf(content->res_snd_ok_inverse.ip,16,"%u.%u.%u.%u",buff[0],buff[1],buff[2],buff[3]);
			content->res_snd_ok_inverse.port = buff[4]+(buff[5]<<8);
			break;
		case P2P_CLI_CALL_MAP:
			snprintf(content->get_udp_map.ip,16,"%u.%u.%u.%u",buff[0],buff[1],buff[2],buff[3]);
			content->get_udp_map.port = buff[4]+(buff[5]<<8);
			break;
		case P2P_SERVER_GETNAT:
			buff[len] = 0;// ensure string ends with null byte
			num = sscanf(buff,"%[^;];%s",content->get_network_nat.id,content->get_network_nat.domain);
			buff[len] = ck_sum;/// restore checksum
			if(num < 2)
			{
				PLOG(ERR,"P2P_SERVER_GETNAT cmd parse error!\n");
				ret = VPI_EPARAMS;
			}
			break;
		case P2P_SERVER_LIST:
			buff[len] = 0;// ensure string ends with null byte
			num = sscanf(buff,"%[^;];%[^*]*%u;%[^*]*%u;%[^*]*%u;%[^*]*%u;",content->res_domain_list.dev_domain,
					content->res_domain_list.dns_1.domain,&content->res_domain_list.dns_1.port,
					content->res_domain_list.dns_2.domain,&content->res_domain_list.dns_2.port,
					content->res_domain_list.p2p_1.domain,&content->res_domain_list.p2p_1.port,
					content->res_domain_list.p2p_2.domain,&content->res_domain_list.p2p_2.port);
			buff[len] = ck_sum;/// restore checksum
			if(num < 9)
			{
				PLOG(ERR,"P2P_SERVER_LIST cmd parse error!\n");
				ret = VPI_EPARAMS;
			}
			break;
		case P2P_SERVER_REG_OK: // 0x90
			buff[len] = 0;// ensure string ends with null byte
			num = sscanf(buff,"%[^*]*%u;%[^*]*%u;%[^;];%[^*]*%u;%[^*]*%u;%[^*]*%u",
					content->reg_server_list.p2p.domain,&content->reg_server_list.p2p.port,
					content->reg_server_list.nat.domain,&content->reg_server_list.nat.port,
					content->reg_server_list.dev_domain,
					content->reg_server_list.dns_1.domain,&content->reg_server_list.dns_1.port,
					content->reg_server_list.dns_2.domain,&content->reg_server_list.dns_2.port,
					content->reg_server_list.update.domain,&content->reg_server_list.update.port);
			buff[len] = ck_sum;/// restore checksum
			if(num < 9)
			{
				PLOG(ERR,"P2P_SERVER_LIST cmd parse error!\n");
				ret = VPI_EPARAMS;
			}
			break;
		case P2P_SERVER_REG_FAIL: // 0x96
			content->reg_fail_dummy = 1;// just set flag
			break;
		case P2P_SERVER_TURN: // 0xC1
			snprintf(content->get_turn_server.ip,16,"%u.%u.%u.%u",buff[0],buff[1],buff[2],buff[3]);
			content->get_turn_server.port = buff[4]+(buff[5]<<8);
			break;
		default:
			ret = VPI_EFAIL;
			break;
	}

	return ret;
}

int p2p_build_package(eproto_role role,sproto_msg *pmsg,unsigned char *sendbuff, int *len)
{
	int ret = 0;
	unsigned char buf_tmp[512] = {0};
	int tmplen = 512;
	unsigned char *tmpbuf = &buf_tmp[0];
	unsigned int cmd = pmsg->cmd;
	sp2p_content *content =(sp2p_content *)pmsg->content;

	if(content == NULL || sendbuff == NULL || len == NULL)
	{
		PLOG(ERR,"invalid params!\n");
		return VPI_EPARAMS;
	}
	if(*len >= tmplen)
	{
		tmpbuf = sendbuff;
		tmplen = *len;
	}
	if(IS_P2P_UPCMD(cmd)&&role==ROLE_DEV)
	{
		tmpbuf[0] = P2P_TOP_1;
		tmpbuf[1] = P2P_TOP_2;
		tmpbuf[2] = tmpbuf[3] = 0xff;// the pack length not data length, little endian
		tmpbuf[4] = cmd&0xff;
		tmplen -= 7;// 5+checksum+end
		ret = build_up_package(cmd,&content->up_content,tmpbuf+5,&tmplen);
		if(ret == 0)
		{
			tmplen += 7;
			tmpbuf[2] = tmplen&0xff;
			tmpbuf[3] = (tmplen>>8)&0xff;
			tmpbuf[tmplen-2] = vpi_calc_checksum(tmpbuf,tmplen-2);
			tmpbuf[tmplen-1] = P2P_END;
		}
	}
	else if(IS_P2P_DOWNCMD(cmd)&&role==ROLE_SERVER)
	{
		tmpbuf[0] = P2P_TOP_1;
		tmpbuf[1] = P2P_TOP_2;
		tmpbuf[2] = tmpbuf[3] = 0xff;// pack length
		tmpbuf[4] = cmd&0xff;
		tmplen -= 7;// 5+checksum+end
		ret = build_down_package(cmd,&content->down_content,tmpbuf+5,&tmplen);
		if(ret == 0)
		{
			tmplen += 7;
			tmpbuf[2] = tmplen&0xff;
			tmpbuf[3] = (tmplen>>8)&0xff;
			tmpbuf[tmplen-2] = vpi_calc_checksum(tmpbuf,tmplen-2);
			tmpbuf[tmplen-1] = P2P_END;
		}
	}
	else
	{
		PLOG(ERR,"cmd 0x%x and role %d is unsupported!\n",cmd,role);
		return VPI_EFAIL;
	}

	if(!ret && *len >= tmplen)
	{// buff is enough big
		PLOG(DBG,"send buff len = %d.\n", tmplen);
		if(tmpbuf != sendbuff)
		{
			memset(sendbuff,0,*len);
			memcpy(sendbuff,tmpbuf,tmplen);
		}
		*len = tmplen;
		vpi_print_package(sendbuff,*len);
	}
	else if(!ret && *len < tmplen)
	{
		PLOG(ERR,"send buff is not enough,%d < %d[need]\n",*len,tmplen);
		ret = VPI_ESPACE;
	}

	return ret;
}

int p2p_parse_package(eproto_role role,sproto_msg *pmsg,unsigned char *recvbuff, int len)
{
	unsigned int *cmd = &pmsg->cmd;
	sp2p_content *content = (sp2p_content *)pmsg->content;
	int data_len = 0;
	int ret = 0;
	unsigned char expected_res_cmd = 0x00;

	if(content == NULL || recvbuff == NULL || len <= 0)
	{
		PLOG(ERR, "invalid params!\n");
		return VPI_EPARAMS;
	}
	
	vpi_print_package(recvbuff,len);
	if(role==ROLE_DEV)
	{
		if(recvbuff[0]!=P2P_TOP_1|| recvbuff[1]!=P2P_TOP_2|| 
			recvbuff[len-1]!=P2P_END) 
		{
			PLOG(ERR,"recv package is invalid in device role.\n");
			ret = VPI_EFORMAT;
		}
		else if(recvbuff[len-2]!=vpi_calc_checksum(recvbuff,len-2))
		{
			PLOG(ERR,"recv package checksum error!\n");
			ret = VPI_ECHECKSUM;
		}
		else if(IS_P2P_UPCMD(*cmd) && // valid current send cmd
				(expected_res_cmd=expected_response_cmd(*cmd))!=0x00 && 
				recvbuff[4]!=expected_res_cmd) // response cmd ok
		{
			PLOG(ERR,"recv cmd 0x%02x is not respond to send cmd 0x%02x\n",recvbuff[4],*cmd);
			ret = VPI_ERESPCMD;
		}
		else
		{
			PLOG(DBG,"recv server ok,parse the content...\n");
			*cmd = recvbuff[4];
			data_len = recvbuff[2] + (recvbuff[3]<<8) - 7;// fixed bytes is 7
			if(data_len > 0) // have params
				ret = parse_down_package(*cmd,&content->down_content,recvbuff+5,data_len);
		}
	}
	else if(role==ROLE_SERVER)
	{
		if(recvbuff[0]!=P2P_TOP_1|| recvbuff[1]!=P2P_TOP_2||
			recvbuff[len-1]!=P2P_END)
		{
			PLOG(ERR,"recv package is invalid in server role.\n");
			ret = VPI_EFORMAT;
		}
		else if(recvbuff[len-2]!=vpi_calc_checksum(recvbuff,len-2))
		{
			PLOG(ERR,"recv package checksum error!\n");
			ret = VPI_ECHECKSUM;
		}
		else if(IS_P2P_DOWNCMD(*cmd) && 
				(expected_res_cmd=expected_response_cmd(*cmd))!=0x00 && 
				recvbuff[4]!=expected_res_cmd) // response cmd ok
		{
			PLOG(ERR,"recv cmd 0x%02x is not respond to send cmd 0x%02x\n",recvbuff[4],*cmd);
			ret = VPI_ERESPCMD;
		}
		else
		{
			PLOG(DBG,"recv device ok,parse the content...\n");
			*cmd = recvbuff[4];
			data_len = recvbuff[2] + (recvbuff[3]<<8) - 7;// pack len - fixed bytes
			PLOG(DBG,"recv cmd = 0x%02x, data len = %d\n",*cmd,data_len);
			if(data_len > 0) // have params
				ret = parse_up_package(*cmd,&content->up_content,recvbuff+5,data_len);
		}
	}
	else
	{
		PLOG(ERR,"role %d or cmd 0x%x is unsupported!\n",role,*cmd);
		ret = VPI_EFAIL;
	}

	return ret;
}
